package checkers_seperate_classes;

import java.util.ArrayList;

/**
 * An object of this class holds data about a game of checkers.
 * It knows what kind of piece is on each square of the checkerboard.
 * Note that RED moves "up" the board (i.e. row number decreases)
 * while BLACK moves "down" the board (i.e. row number increases).
 * Methods are provided to return lists of available legal moves.
 */
public class CheckersData {

	/*  The following constants represent the possible contents of a square
      on the board.  The constants RED and BLACK also represent players
      in the game. */

	public static enum SquareContent {
	EMPTY,
	RED,
	RED_KING,
	BLACK,
	BLACK_KING;

	public SquareContent toLower() {
		SquareContent rtn = this;
		if (rtn == BLACK_KING) {
			rtn = BLACK;
		} else if (rtn == RED_KING) {
			rtn = RED;
		}
		return rtn;
	}};


	SquareContent[][] board;  // board[r][c] is the contents of row r, column c.  

	private SquareContent pieceTaken;


	private Boolean wasPiecePromatedToKing;


	private CheckersMove lastMove;


	private int numberOfMoves;

	private final  SquareContent pieceFirstMove;


	private SquareContent lastPieceMoved;

	/**
	 * Constructor.  Create the board and set it up for a new game.
	 */
	public CheckersData(SquareContent firstMove) {
		pieceFirstMove = firstMove;
		lastPieceMoved = null;
		board = new SquareContent[8][8];
		setUpGame();
	}

	public CheckersData(CheckersData aThat) {
		super();
		if (aThat == null) {
			this.board = null;
			this.pieceTaken = SquareContent.EMPTY;
			this.wasPiecePromatedToKing = false;
			this.lastMove = null;
			this.pieceFirstMove = null;
			numberOfMoves = 0;
		} else {
			this.pieceFirstMove = aThat.pieceFirstMove;
			this.numberOfMoves = aThat.numberOfMoves;
			board = new SquareContent[8][8];
			for (int i=0; i<8; i++) {
				for (int j=0; j<8; j++) {
					board[i][j] = aThat.board[i][j];
				}
			}
			this.pieceTaken = aThat.pieceTaken;
			this.wasPiecePromatedToKing = aThat.wasPiecePromatedToKing;
			if (aThat.lastMove == null) {
				this.lastMove = null;
			} else {
				this.lastMove = aThat.lastMove.clone();
			}
		}
	}

	/**
	 * Set up the board with checkers in position for the beginning
	 * of a game.  Note that checkers can only be found in squares
	 * that satisfy  row % 2 == col % 2.  At the start of the game,
	 * all such squares in the first three rows contain black squares
	 * and all such squares in the last three rows contain red squares.
	 */
	public void setUpGame() {
		numberOfMoves = 0;
		pieceTaken = SquareContent.EMPTY;
		wasPiecePromatedToKing = false;
		for (int row = 0; row < 8; row++) {
			for (int col = 0; col < 8; col++) {
				if ( row % 2 == col % 2 ) {
					if (row < 3)
						board[row][col] = SquareContent.BLACK;
					else if (row > 4)
						board[row][col] = SquareContent.RED;
					else
						board[row][col] = SquareContent.EMPTY;
				}
				else {
					board[row][col] = SquareContent.EMPTY;
				}
			}
		}
	}  // end setUpGame()


	/**
	 * Return the contents of the square in the specified row and column.
	 */
	public SquareContent pieceAt(int row, int col) {
		return board[row][col];
	}


	/**
	 * Make the specified move.  It is assumed that move
	 * is non-null and that the move it represents is legal.
	 */
	public void makeMove(CheckersMove move) {
		makeMove(move.getFromRow(), move.getFromCol(), move.getToRow(), move.getToCol());
	}

	CheckersMove getLastMove() {
		return lastMove;
	}


	/**
	 * Make the move from (fromRow,fromCol) to (toRow,toCol).  It is
	 * assumed that this move is legal.  If the move is a jump, the
	 * jumped piece is removed from the board.  If a piece moves to
	 * the last row on the opponent's side of the board, the 
	 * piece becomes a king.
	 */
	void makeMove(int fromRow, int fromCol, int toRow, int toCol) {
		lastMove = new CheckersMove(fromRow, fromCol, toRow, toCol);
		final SquareContent pieceMoving = board[fromRow][fromCol];
		boolean wasNumberOfMovesIncremented = false;
		if (pieceMoving.toLower() == pieceFirstMove.toLower() 
				&& (lastPieceMoved == null
				|| pieceMoving.toLower() != lastPieceMoved.toLower())) {
			numberOfMoves++;
			wasNumberOfMovesIncremented = true;
		} 
		lastPieceMoved = pieceMoving;
		pieceTaken = SquareContent.EMPTY;
		wasPiecePromatedToKing = false;
		// Move the piece
		board[toRow][toCol] = board[fromRow][fromCol];
		board[fromRow][fromCol] = SquareContent.EMPTY;
		// Was this a jump
		if (Math.abs(fromRow - toRow) == 2 ) {
			// The move is a jump.  Remove the jumped piece from the board.
			int jumpRow = (fromRow + toRow) / 2;  // Row of the jumped piece.
			int jumpCol = (fromCol + toCol) / 2;  // Column of the jumped piece.
			pieceTaken = board[jumpRow][jumpCol];
			board[jumpRow][jumpCol] = SquareContent.EMPTY;
			CheckersMove moves[] = this.getLegalMoves(pieceMoving);
			if (moves != null) {
				boolean hasMoreJumps = false;
				for (CheckersMove aMove:moves) {
					if (aMove.isJump() 
							&& aMove.getFromCol() == lastMove.getToCol() 
							&& aMove.getFromRow() == lastMove.getToRow() ) {
						hasMoreJumps = true;
						break;
					}
				}
				if (wasNumberOfMovesIncremented && hasMoreJumps) {
					numberOfMoves--;
				}
			}
		}
		// Was this a king promotion
		if (toRow == 0 && board[toRow][toCol] == SquareContent.RED) {
			board[toRow][toCol] = SquareContent.RED_KING;
			wasPiecePromatedToKing = true;
		} else if (toRow == 7 && board[toRow][toCol] == SquareContent.BLACK) {
			board[toRow][toCol] = SquareContent.BLACK_KING;
			wasPiecePromatedToKing = true;
		}
	}

	/**
	 * Return an array containing all the legal CheckersMoves
	 * for the specified player on the current board.  If the player
	 * has no legal moves, null is returned.  The value of player
	 * should be one of the constants RED or BLACK; if not, null
	 * is returned.  If the returned value is non-null, it consists
	 * entirely of jump moves or entirely of regular moves, since
	 * if the player can jump, only jumps are legal moves.
	 */
	public CheckersMove[] getLegalMoves(SquareContent player) {

		if (player != SquareContent.RED && player != SquareContent.BLACK)
			return null;

		SquareContent playerKing;  // The constant representing a King belonging to player.
		if (player == SquareContent.RED)
			playerKing = SquareContent.RED_KING;
		else
			playerKing = SquareContent.BLACK_KING;

		ArrayList<CheckersMove> moves = new ArrayList<CheckersMove>();  // Moves will be stored in this list.

		/*  First, check for any possible jumps.  Look at each square on the board.
      If that square contains one of the player's pieces, look at a possible
      jump in each of the four directions from that square.  If there is 
      a legal jump in that direction, put it in the moves ArrayList.
		 */

		for (int row = 0; row < 8; row++) {
			for (int col = 0; col < 8; col++) {
				if (board[row][col] == player || board[row][col] == playerKing) {
					if (canJump(player, row, col, row+1, col+1, row+2, col+2))
						moves.add(new CheckersMove(row, col, row+2, col+2));
					if (canJump(player, row, col, row-1, col+1, row-2, col+2))
						moves.add(new CheckersMove(row, col, row-2, col+2));
					if (canJump(player, row, col, row+1, col-1, row+2, col-2))
						moves.add(new CheckersMove(row, col, row+2, col-2));
					if (canJump(player, row, col, row-1, col-1, row-2, col-2))
						moves.add(new CheckersMove(row, col, row-2, col-2));
				}
			}
		}

		/*  If any jump moves were found, then the user must jump, so we don't 
      add any regular moves.  However, if no jumps were found, check for
      any legal regular moves.  Look at each square on the board.
      If that square contains one of the player's pieces, look at a possible
      move in each of the four directions from that square.  If there is 
      a legal move in that direction, put it in the moves ArrayList.
		 */

		if (moves.size() == 0) {
			for (int row = 0; row < 8; row++) {
				for (int col = 0; col < 8; col++) {
					if (board[row][col] == player || board[row][col] == playerKing) {
						if (canMove(player,row,col,row+1,col+1))
							moves.add(new CheckersMove(row,col,row+1,col+1));
						if (canMove(player,row,col,row-1,col+1))
							moves.add(new CheckersMove(row,col,row-1,col+1));
						if (canMove(player,row,col,row+1,col-1))
							moves.add(new CheckersMove(row,col,row+1,col-1));
						if (canMove(player,row,col,row-1,col-1))
							moves.add(new CheckersMove(row,col,row-1,col-1));
					}
				}
			}
		}

		/* If no legal moves have been found, return null.  Otherwise, create
      an array just big enough to hold all the legal moves, copy the
      legal moves from the ArrayList into the array, and return the array. */

		if (moves.size() == 0)
			return null;
		else {
			CheckersMove[] moveArray = new CheckersMove[moves.size()];
			for (int i = 0; i < moves.size(); i++)
				moveArray[i] = moves.get(i);
			return moveArray;
		}

	}  // end getLegalMoves


	/**
	 * Return a list of the legal jumps that the specified player can
	 * make starting from the specified row and column.  If no such
	 * jumps are possible, null is returned.  The logic is similar
	 * to the logic of the getLegalMoves() method.
	 */
	public CheckersMove[] getLegalJumpsFrom(SquareContent player, int row, int col) {
		if (player != SquareContent.RED && player != SquareContent.BLACK)
			return null;
		SquareContent playerKing;  // The constant representing a King belonging to player.
		if (player == SquareContent.RED)
			playerKing = SquareContent.RED_KING;
		else
			playerKing = SquareContent.BLACK_KING;
		ArrayList<CheckersMove> moves = new ArrayList<CheckersMove>();  // The legal jumps will be stored in this list.
		if (board[row][col] == player || board[row][col] == playerKing) {
			if (canJump(player, row, col, row+1, col+1, row+2, col+2))
				moves.add(new CheckersMove(row, col, row+2, col+2));
			if (canJump(player, row, col, row-1, col+1, row-2, col+2))
				moves.add(new CheckersMove(row, col, row-2, col+2));
			if (canJump(player, row, col, row+1, col-1, row+2, col-2))
				moves.add(new CheckersMove(row, col, row+2, col-2));
			if (canJump(player, row, col, row-1, col-1, row-2, col-2))
				moves.add(new CheckersMove(row, col, row-2, col-2));
		}
		if (moves.size() == 0)
			return null;
		else {
			CheckersMove[] moveArray = new CheckersMove[moves.size()];
			for (int i = 0; i < moves.size(); i++)
				moveArray[i] = moves.get(i);
			return moveArray;
		}
	}  // end getLegalMovesFrom()


	/**
	 * This is called by the two previous methods to check whether the
	 * player can legally jump from (r1,c1) to (r3,c3).  It is assumed
	 * that the player has a piece at (r1,c1), that (r3,c3) is a position
	 * that is 2 rows and 2 columns distant from (r1,c1) and that 
	 * (r2,c2) is the square between (r1,c1) and (r3,c3).
	 */
	private boolean canJump(SquareContent player, int r1, int c1, int r2, int c2, int r3, int c3) {

		if (r3 < 0 || r3 >= 8 || c3 < 0 || c3 >= 8)
			return false;  // (r3,c3) is off the board.

		if (board[r3][c3] != SquareContent.EMPTY)
			return false;  // (r3,c3) already contains a piece.

		if (player == SquareContent.RED) {
			if (board[r1][c1] == SquareContent.RED && r3 > r1)
				return false;  // Regular red piece can only move up.
			if (board[r2][c2] != SquareContent.BLACK && board[r2][c2] != SquareContent.BLACK_KING)
				return false;  // There is no black piece to jump.
			return true;  // The jump is legal.
		}
		else {
			if (board[r1][c1] == SquareContent.BLACK && r3 < r1)
				return false;  // Regular black piece can only move down.
			if (board[r2][c2] != SquareContent.RED && board[r2][c2] != SquareContent.RED_KING)
				return false;  // There is no red piece to jump.
			return true;  // The jump is legal.
		}

	}  // end canJump()


	/**
	 * This is called by the getLegalMoves() method to determine whether
	 * the player can legally move from (r1,c1) to (r2,c2).  It is
	 * assumed that (r1,r2) contains one of the player's pieces and
	 * that (r2,c2) is a neighboring square.
	 */
	private boolean canMove(SquareContent player, int r1, int c1, int r2, int c2) {

		if (r2 < 0 || r2 >= 8 || c2 < 0 || c2 >= 8)
			return false;  // (r2,c2) is off the board.

		if (board[r2][c2] != SquareContent.EMPTY)
			return false;  // (r2,c2) already contains a piece.

		if (player == SquareContent.RED) {
			if (board[r1][c1] == SquareContent.RED && r2 > r1)
				return false;  // Regular red piece can only move down.
			return true;  // The move is legal.
		}
		else {
			if (board[r1][c1] == SquareContent.BLACK && r2 < r1)
				return false;  // Regular black piece can only move up.
			return true;  // The move is legal.
		}

	}  // end canMove()


	public double calculateScore(SquareContent aPlayer) {
		double rtn = calculateRedScore();
		if (aPlayer == SquareContent.BLACK) {
			rtn = -rtn;
		}
		return rtn;
	}
	/**
	 * The pieces score 1 plus 0.1 times the row
	 * THe kings score 3
	 * @return score
	 */
	public double calculateRedScore() {
		double score = 0;
		final int piece_score = 1;
		final int king_score = 3*piece_score;
		final int nrows = 8, ncols = 8;
		for (int row = 0; row < nrows; row++) {
			for (int col = 0; col < ncols; col++) {
				final SquareContent piece = board[row][col];
				switch(piece) {
				case RED:
					score += piece_score + (nrows-1-row)*0.1;
					break;
				case RED_KING:
					score += king_score;
					break;
				case BLACK:
					score -= piece_score + (row)*0.1;
					break;
				case BLACK_KING:
					score -= king_score;
					break;
				case EMPTY:
					break;
				default:
					throw new IllegalStateException("Unknown square content"+piece.toString());
				}
			}
		}
		return (double)Math.round(score * 100) / 100;
	}

	public SquareContent getPieceTaken() {
		return pieceTaken;
	}


	public String getNameOfPieceTaken() {
		String name = "";
		switch(pieceTaken) {
		case RED:
			name = "RED";
			break;
		case RED_KING:
			name = "RED KING";
			break;
		case BLACK:
			name = "BLACK";
			break;
		case BLACK_KING:
			name = "BLACK KING";
			break;
		case EMPTY:
			break;
		default:
			throw new IllegalStateException("Unknown piece "+pieceTaken);
		}
		return name;
	}

	public Boolean wasPiecePromotedToKing() {
		return wasPiecePromatedToKing;
	}

	public void makeUnMove(CheckersMove move, String pieceTakenStr, String previousPieceTakenStr,
			boolean promotedToKing, boolean previousToKing) {
		makeUnMove(move.getFromRow(), move.getFromCol(), move.getToRow(), move.getToCol(), 
				pieceTakenStr, previousPieceTakenStr,
				promotedToKing, previousToKing);
	}

	/**
	 * Unmake the move from (fromRow,fromCol) to (toRow,toCol).  It is
	 * assumed that this move is legal.  If the move is a jump, the
	 * jumped piece is removed from the board.  If a piece moves to
	 * the last row on the opponent's side of the board, the 
	 * piece becomes a king.
	 */
	void makeUnMove(int fromRow, int fromCol, int toRow, int toCol, String pieceTakenStr,
			String previousPieceTakenStr,
			boolean promotedToKing, boolean previousToKing) {
		pieceTaken = SquareContent.EMPTY;
		wasPiecePromatedToKing = false;
		// Move the piece back
		board[fromRow][fromCol] = board[toRow][toCol];
		board[toRow][toCol] = SquareContent.EMPTY;
		// Was this a jump
		if (Math.abs(fromRow - toRow) == 2 ) {
			// The move is a jump.  Remove the jumped piece from the board.
			int jumpRow = (fromRow + toRow) / 2;  // Row of the jumped piece.
			int jumpCol = (fromCol + toCol) / 2;  // Column of the jumped piece.
			board[jumpRow][jumpCol] = CheckersData.toInt(pieceTakenStr);
		}
		pieceTaken = CheckersData.toInt(previousPieceTakenStr);
		// Was this a king promotion
		if (promotedToKing && board[fromRow][fromCol] == SquareContent.RED_KING) {
			board[fromRow][fromCol] = SquareContent.RED;
			wasPiecePromatedToKing = previousToKing; 
		} else if (promotedToKing && board[fromRow][fromCol] == SquareContent.BLACK_KING) {
			board[fromRow][fromCol] = SquareContent.BLACK;
			wasPiecePromatedToKing = previousToKing;
		}
	}

	public static SquareContent toInt(String str) {
		SquareContent rtn;
		switch (str.toLowerCase()) {
		case "empty":
			rtn = SquareContent.EMPTY;
			break;
		case "red":
			rtn = SquareContent.RED;
			break;
		case "red king":
		case "red_king":
			rtn = SquareContent.RED_KING;
			break;
		case "black":
			rtn = SquareContent.BLACK;
			break;
		case "black king":
		case "black_king":
			rtn = SquareContent.BLACK_KING;
			break;
		default:
			if (str.isEmpty()) {
				rtn = SquareContent.EMPTY;
			} else {
				throw new IllegalArgumentException(String.format("Unable to convert '%s' to int", str));
			}
			break;
		}
		return rtn;
	}


	public static String toName(SquareContent aValue) {
		String rtn="";
		switch (aValue) {
		case EMPTY:
			rtn = "EMPTY";
			break;
		case RED:
			rtn = "RED";
			break;
		case RED_KING:
			rtn = "RED KING";
			break;
		case BLACK:
			rtn = "BLACK";
			break;
		case BLACK_KING:
			rtn = "BLACK KING";
			break;
		default:
			throw new IllegalArgumentException("Unable to convert to string the value "+aValue);
		}
		return rtn;
	}


	public CheckersData clone() {
		CheckersData rtn = new CheckersData(this);
		return rtn;
	}


	public static SquareContent getOtherPlayer(SquareContent curPlayer) {
		SquareContent otherPlayer = SquareContent.RED;
		if (curPlayer == SquareContent.RED) {
			otherPlayer = SquareContent.BLACK;
		}
		return otherPlayer;
	}


	public SquareContent getLastMoveMadeBy() {
		SquareContent lastPlayer = SquareContent.RED;
		if (lastMove != null) {
			final int toRow = lastMove.getToRow();
			final int toCol = lastMove.getToCol();
			final SquareContent lastPiece = pieceAt(toRow, toCol);
			if (lastPiece == SquareContent.BLACK || lastPiece == SquareContent.BLACK_KING) {
				lastPlayer = SquareContent.BLACK;
			}
		}
		return lastPlayer;
	}


	public SquareContent getNextMoveWillBeMadeBy() {
		final SquareContent lastPlayer = getLastMoveMadeBy();
		SquareContent nextPlayer = lastPlayer;			
		if (lastMove != null) {
			final boolean wasJump = lastMove.isJump();
			if (!wasJump) {
				nextPlayer = getOtherPlayer(lastPlayer);
			}
		}
		return nextPlayer;
	}

	public int getMoveNumber() {
		return this.numberOfMoves;
	}

} // end class CheckersData
